package sqltool

import (
	"go/ast"
	"go/parser"
	"go/token"
	"io/ioutil"
	"os"
	"reflect"
)

// ex系列的goparser会增加多注释,tag这些信息
type GoCodeInfoEx struct {
	PkgName string
	Imports []string
	Structs []GoStructEx
	Funcs   []GoFuncEx
}

type GoStructEx struct {
	Name          string
	Type          string
	FieldNames    []string
	FieldTypes    []string
	FieldTags     []string
	FieldComments []string
	Doc           string
	Comment       string
}

type GoFuncEx struct {
	Name               string
	RecvName, RecvType string
	ParamNames         []string
	ParamTypes         []GoParamType
	RetTypes           []string
	Doc                string
	Comment            string
}

type GoParamType struct {
	Type       string
	structInfo *GoStructEx
}

func ParseGoEx(goCode string) (info GoCodeInfoEx, err error) {
	goCodeBuf := []byte(goCode)
	file, err := ioutil.TempFile("", "tmpfile")
	if err != nil {
		panic(err)
	}
	defer func() { _ = os.Remove(file.Name()) }()
	if _, err := file.Write([]byte(goCode)); err != nil {
		panic(err)
	}
	fset := token.NewFileSet()
	f, err := parser.ParseFile(fset, file.Name(), nil, parser.ParseComments)
	if err != nil {
		return GoCodeInfoEx{}, err
	}
	gofile := GoCodeInfoEx{
		Imports: make([]string, 0),
		Structs: make([]GoStructEx, 0),
		Funcs:   make([]GoFuncEx, 0),
	}
	ast.Inspect(f, func(node ast.Node) bool {
		nodeStr := func(nd ast.Node) string {
			rv := reflect.ValueOf(nd)
			if rv.IsNil() {
				return ""
			}
			return string(goCodeBuf[nd.Pos()-1 : nd.End()-1])
		}
		// package
		if impSp, ok := node.(*ast.ImportSpec); ok {
			gofile.Imports = append(gofile.Imports, impSp.Path.Value)
			return true
		}
		// import
		if af, ok := node.(*ast.File); ok {
			gofile.PkgName = af.Name.Name
			return true
		}
		// type struct
		if gd, ok := node.(*ast.GenDecl); ok && gd.Tok == token.TYPE {
			if ts, ok1 := gd.Specs[0].(*ast.TypeSpec); ok1 {
				gs := GoStructEx{
					Name: ts.Name.Name,
				}
				if st, ok := ts.Type.(*ast.StructType); ok {
					gs.Type = "struct"
					pNames, pTypes, pTags, pComments := make([]string, 0), make([]string, 0), make([]string, 0), make([]string, 0)
					for _, f := range st.Fields.List {
						for _, name := range f.Names {
							pNames = append(pNames, nodeStr(name))
							pTypes = append(pTypes, nodeStr(f.Type))
							pTags = append(pTags, nodeStr(f.Tag))
							pComments = append(pComments, nodeStr(f.Doc))
						}
					}
					gs.FieldNames = pNames
					gs.FieldTypes = pTypes
					gs.FieldTags = pTags
					gs.FieldComments = pComments
				} else {
					gs.Type = nodeStr(ts.Type)
				}
				gs.Doc = nodeStr(gd.Doc)
				gofile.Structs = append(gofile.Structs, gs)
			}
			return true
		}
		// functions
		if fd, ok := node.(*ast.FuncDecl); ok {
			sf := GoFuncEx{}
			sf.Name = fd.Name.Name
			// recv
			if fd.Recv != nil && len(fd.Recv.List) > 0 {
				if len(fd.Recv.List[0].Names) > 0 {
					sf.RecvName = fd.Recv.List[0].Names[0].String()
				}
				sf.RecvType = nodeStr(fd.Recv.List[0].Type)
			}
			// results
			if fd.Type.Results != nil && len(fd.Type.Results.List) > 0 {
				rts := make([]string, 0)
				for _, tr := range fd.Type.Results.List {
					rts = append(rts, nodeStr(tr.Type))
				}
				sf.RetTypes = rts
			}
			// params
			if fd.Type.Params != nil && len(fd.Type.Params.List) > 0 {
				pNames := make([]string, 0)
				pTypes := make([]GoParamType, 0)
				for _, p := range fd.Type.Params.List {
					pType := GoParamType{}
					if st, ok := p.Type.(*ast.StructType); ok {
						pType.Type = "struct"
						anonymousStruct := GoStructEx{
							Name:          "",
							Type:          "struct",
							FieldNames:    make([]string, 0),
							FieldTypes:    make([]string, 0),
							FieldTags:     make([]string, 0),
							FieldComments: make([]string, 0),
							Comment:       nodeStr(p.Comment),
							Doc:           nodeStr(p.Doc),
						}
						for _, sf := range st.Fields.List {
							sft := nodeStr(sf.Type)
							sftag := nodeStr(sf.Tag)
							sfc := nodeStr(sf.Comment)
							for _, name := range sf.Names {
								anonymousStruct.FieldNames = append(anonymousStruct.FieldNames, nodeStr(name))
								anonymousStruct.FieldTypes = append(anonymousStruct.FieldTypes, sft)
								anonymousStruct.FieldTags = append(anonymousStruct.FieldTags, sftag)
								anonymousStruct.FieldComments = append(anonymousStruct.FieldComments, sfc)
							}
						}
						pType.structInfo = &anonymousStruct
					} else {
						pType.Type = nodeStr(p.Type)
					}
					for _, pName := range p.Names {
						pNames = append(pNames, pName.Name)
						pTypes = append(pTypes, pType)
					}
				}
				sf.ParamNames = pNames
				sf.ParamTypes = pTypes
			}

			// comment
			sf.Doc = nodeStr(fd.Doc)
			gofile.Funcs = append(gofile.Funcs, sf)
			return true
		}
		return true
	})
	return gofile, nil
}
